#ifndef SOCKS5_ACTION_CLIENT_H
#define SOCKS5_ACTION_CLIENT_H
#pragma once

#include <KSockAddress.h>
#include <KStorage.h>
#include <KTimerQueue.h>
#include "S5Action.h"
#include <cstdint>

class S5ActionServer;

class S5ActionClient : public S5Action, KTimer {
public:
    enum State {
        TIMEOUT = 0,
        VER_1, NMETHODS, METHODS,
        VER_2, ULEN, UNAME, PLEN, PASSWD,
        VER_3, CMD, RSV, ATYP, DLEN, DST_ADDR, DST_PORT,
        EVAL,
        TCP_RELAY, UDP_RELAY
    };
public:
    S5ActionClient(const KIAddress &addr, S5ActionServer *server);
    ~S5ActionClient();
public:
    const KIpAddress &address() const   { return m_addr; }
    int command() const                 { return m_command; }
    State state() const                 { return m_state; }
    std::string domain() const;
    const std::string &username() const { return m_username; }
public:
    void relay(const void *data, size_t length, const KIAddress *dest);
protected:
    enum Result { Abort, Idle, Receive };
    Result process(void *data, size_t length);
    Result evaluate();
    void postFailure(uint8_t rep, bool clear = true);
    void postSuccess(const KIpAddress &addr, bool wait = false);
protected:
    void abortImp();
    void onClosing(bool stopped);
    void onError(KDef::SockError type, DWORD error);
    void onReceived(KKey *&key, void *data, size_t length, bool more);
    void onSent(KKey *&key, void *data, size_t length);
    void tick();
private:
    bool            m_abortOnSent;
    KIpAddress      m_addr;
    S5ActionServer *m_server;
    S5Action *      m_proxy_receiver; // For UDP_RELAY
    S5Action *      m_proxy_sender;
    // For negotiation only
    KStorage        m_storage;
    State           m_state;
    size_t          m_length;
    std::string     m_domain;
    std::string     m_username;
    std::string     m_password;
    uint8_t         m_command;
    uint8_t         m_addr_type;
    u_long          m_dest_addr;
    u_short         m_dest_port;
    friend class    S5ActionBind;
    friend class    S5ActionConnect;
};

#endif // SOCKS5_ACTION_CLIENT_H